#include <GL/glew.h>

#include "Map.h"
#include "../log.h"
#include "../common.h"
#include "../drawing.h"

#include <map>
#include <string>
#include <math.h>

namespace Tmpl 
{

// Float Packing
void MapPos::SetNormal(const Vec3f& n)
{
	mNormal = 0;
	for(unsigned int i=0; i<3; ++i)
	{
		const unsigned int flt = (int)(n[i] * 511.0f);
		mNormal |= (flt & 0x3FF) << (i*10);
	}
}
Vec3f MapPos::GetNormal() const
{
	Vec3f result;
	for(unsigned int i=0; i<3; ++i)
	{
		const unsigned int bits = (mNormal >> (i*10)) & 0x3FF;
		const unsigned int flt = bits | 0xFFFFFE00 * (bits >> 9);
		result[i] = (float)((int)flt) * (1.0f / 511.0f);
	}
	return result;
}

// Map
Map::Map()
{
	mSeed = 0;
	mMapSize = 0;
	mMap = 0;
	mZoneCount = 0;
	mZones = 0;
}

Map::~Map()
{
	if (mMap) delete [] mMap;
	if (mZones) 
	{
		for (int i=0; i<mZoneCount; ++i) delete mZones[i];
		delete [] mZones;
	}
}

// Zones
void Map::AddZoneType(MapZoneType* zoneType)
{
	// add zone type
	mZoneTypes.push_back(zoneType);

	// add textures
	for (int i=0; i<zoneType->GetTextureCount(); ++i)
	{
		std::string name = zoneType->GetTexture(i)->GetName();
		bool valid = true;
		for (int j=0; j<(int)mTextures.size(); ++j)
		{
			if (name == mTextures[j])
			{
				zoneType->GetTexture(i)->SetTextureId(j);
				valid = false;
				break;
			}
		}
		if (valid)
		{
			mTextures.push_back(name);
			zoneType->GetTexture(i)->SetTextureId(mTextures.size() - 1);
		}
	}
}
// Map
float Map::GetHeightAtPos(Vec2f pos)
{
	// Resolve Player Cell
	float x = pos.x;
	float y = pos.y;

	int x0 = (int)x;
	int x1 = x0 + 1;
	int y0 = (int)y;
	int y1 = y0 + 1;

	//Look up the height values
	float h00 = GetMapPosSafe(x0,y0).height;
	float h01 = GetMapPosSafe(x0,y1).height;
	float h10 = GetMapPosSafe(x1,y0).height;
	float h11 = GetMapPosSafe(x1,y1).height;

	float tx = x - float(x0);
	float ty = y - float(y0);

	//Use a bilerp to find the final height value
	float txty = tx * ty;
	return h00 * ( 1.0f - ty - tx + txty ) + h10 * ( tx - txty ) + h11 * txty + h01 * ( ty - txty );

	/*
	float x = pos.x;
	float y = pos.y;
	int px = (int)x;
	int py = (int)y;

	//Look up the height values
	float h1 = GetMapPosSafe(px  ,py  ).height;
	float h2 = GetMapPosSafe(px+1,py  ).height;
	float h3 = GetMapPosSafe(px  ,py+1).height;
	float h4 = GetMapPosSafe(px+1,py+1).height;

	float fx = x - float(px); 
	float fy = y - float(py); 
	float fx1 = 1.0f - fx; 
	float fy1 = 1.0f - fy; 

	float w1 = fx1 * fy1; 
	float w2 = fx * fy1; 
	float w3 = fx1 * fy; 
	float w4 = fx * fy; 
	return h1 * w1 + h2 * w2 + h3 * w3 + h4 * w4
		*/
}

MapZone* Map::GetZoneAtPos(Vec2f pos)
{
	MapPos& map = GetMapPosSafe(int(pos.x+0.5f), int(pos.y+0.5f));
	return mZones[map.zone];
}

MapPos* Map::GetMapPos(int x, int y)
{
	if (x >= 0 && y >= 0 && x < mMapSize && y < mMapSize)
	{
		return &mMap[y * mMapSize + x];
	}
	return 0;
}

MapPos& Map::GetMapPosSafe(int x, int y)
{
	int mapX = Clamp(0, mMapSize-1, x);
	int mapY = Clamp(0, mMapSize-1, y);
	return mMap[mapY * mMapSize + mapX];
}

// Generation
inline Vec3f GetNormal(Vec3f p0, Vec3f p1, Vec3f p2)
{
	Vec3f v1 = p0 - p2;
	Vec3f v2 = p0 - p1;
	v1 = v1.Cross(v2);
	return v1.Normalized();
}
static inline float SCurve(float a)
{
    return (a * a * (3.0f - 2.0f * a));
}
static inline int Noise2D(int x, int y)
{     
	int n = (1619 * x + 31337 * y) & 0x7fffffff;
    n = (n >> 13) ^ n;
	n = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff;
	return n;
}
static inline float NoiseToFloat(int n)
{
	return 1.0f - ((float)(n) / 1073741824.0f);
}
static float CoherentNoise2D(float x, float y)  
{  
	int ix = (int)x;
	int iy = (int)y;
	
	float s = NoiseToFloat(Noise2D(ix  , iy	 ));   
	float t = NoiseToFloat(Noise2D(ix+1, iy	 ));   
	float u = NoiseToFloat(Noise2D(ix  , iy+1)); 
	float v = NoiseToFloat(Noise2D(ix+1, iy+1)); 
	
	float sx = SCurve(x - float(ix));
	float iu = Lerp(s, t, sx);
	float iv = Lerp(u, v, sx);
	
	float sy = SCurve(y - float(iy));
	return Lerp(iu, iv, sy);
} 

static float TurbulenceNoise(float x, float y, float persistance, int octaves) 
{
	float total = 0.0;
	float frequency = 1.0f;
	float amplitude = persistance;
	for (int i=0; i<octaves; ++i)
	{
		total += fabs(CoherentNoise2D(x * frequency, y * frequency) * amplitude);
		amplitude *= persistance;
		frequency *= 2.0f;
	}
	return total;
}
static float FractalNoise(float x, float y, float persistance, int octaves) 
{
	float total = 0.0;
	float frequency = 1.0f;
	float amplitude = persistance;
	for (int i=0; i<octaves; ++i)
	{
		total += CoherentNoise2D(x * frequency, y * frequency) * amplitude;
		amplitude *= persistance;
		frequency *= 2.0f;
	}
	return total;
}
static float TerrainGetHeightAtPos(float x, float y, float roughness, float mountains, float frequency)
{
	float heightScalar = 0.5f;

	float mfreq = 0.005f+frequency*0.1f;
	float m = Max(0.0f, TurbulenceNoise(x*mfreq, y*mfreq, 0.5f, 3)*6.0f*heightScalar*(mountains+0.1f) - (1.25f+mountains*0.5f)*heightScalar);

	float roughnessScalar = 0.2f+roughness*0.1f;
	float r = CoherentNoise2D(x*roughnessScalar, y*roughnessScalar) * (roughness*0.3f) * heightScalar;

	return r + m;
}
static float TerrainGetTextureAtPos(float x, float y, int texCount, float density)
{
	float xx = x + float(texCount*23244);
	float yy = y + float(texCount*997);
	float turbulence = Max(0.00f, TurbulenceNoise(xx*0.15f, yy*0.15f, 0.5f, 3)*2.0f - 0.75f);
	float noise = Max(0.00f, FractalNoise(xx*0.1f, yy*0.1f, 0.5f, 3)*2.0f) * density;
	float bump = Max(0.00f, CoherentNoise2D(xx*0.2f, yy*0.2f)*0.3f) * density;
	return Clamp(0.01f, 1.0f, turbulence + noise + bump);
}
void Map::GenerateMap(int seed, ZonesCountType zoneCount, int size)
{
	mSeed = seed;
	srand(mSeed);

	// allocate map
	mMapSize = size;
	int mapSizeSqr = mMapSize*mMapSize;
	float fMapSize = float(mMapSize);
	mMap = new MapPos[mapSizeSqr];
	Log::Print("Map File Size: %2.2f mb", float(sizeof(MapPos)*mapSizeSqr)/1024.0f/1024.0f);

	// generate zones
	Log::Print("Generating Zones....");
	mZoneCount = 0;
	mZones = new MapZone*[zoneCount];

	float offset = sqrt(float(zoneCount));
	float range = float(mMapSize) / offset;
	int stepCount = int(offset);

	for (int y=0; y<stepCount; ++y)
	for (int x=0; x<stepCount; ++x)
	{
		Vec2f pos = Vec2f(x * range + RandF(range), y * range + RandF(range));
		mZones[mZoneCount++] = new MapZone(mZoneTypes[rand() % mZoneTypes.size()], pos);
	}
	while (mZoneCount < zoneCount)
	{
		mZones[mZoneCount++] = new MapZone(mZoneTypes[rand() % mZoneTypes.size()], Vec2f(RandF(fMapSize), RandF(fMapSize)));
	}

	// Assign Zones
	Log::Print("Assigning Zones....");
	int idx = 0;
	for (float y=0; y<fMapSize; ++y)
	for (float x=0; x<fMapSize; ++x)
	{
		MapPos& m = mMap[idx++];
		m.height = 0.0f;
		for (int i=0; i<MAX_TEXTURES_PER_POS; ++i)
		{
			m.textures[i].texId = 255;
		}

		// Get the nearest zone
		Vec2f pos = Vec2f(x,y);
		float distance = 1000000000.0f;
		for (int i=0; i<mZoneCount; ++i)
		{
			float d = (mZones[i]->GetPos() - pos).SqrLength();
			if (distance > d)
			{
				distance = d;
				m.zone = i;
			}
		}
	}

	// Calculate Terrain height
	Log::Print("Calculating Heights....");
	for (int yy=-5; yy<=5; ++yy)
	for (int xx=-5; xx<=5; ++xx)
	{
		for (int yyy=0; yyy<mMapSize; ++yyy)
		for (int xxx=0; xxx<mMapSize; ++xxx)
		{
			// get the idx
			int x = xx + xxx;
			int y = yy + yyy;
			if ( x < 0 || y < 0 || x >= mMapSize || y >= mMapSize) continue;

			// Calculate the height
			MapZoneType* zoneType = mZones[mMap[y * mMapSize + x].zone]->GetType();
			float roughness = zoneType->GetRoughness();
			float mountains = zoneType->GetMountains();
			float mfreq = zoneType->GetMountainFrequency();
			mMap[yyy * mMapSize + xxx].height += TerrainGetHeightAtPos(float(x), float(y), roughness, mountains, mfreq);
		}
	}

	// smooth the heightmap
	Log::Print("Smoothing....");
	for (int yyy=0; yyy<mMapSize; ++yyy)
	for (int xxx=0; xxx<mMapSize; ++xxx)
	{
		int count = 0;
		float height = 0.0f;
		for (int yy=-1; yy<=1; ++yy)
		for (int xx=-1; xx<=1; ++xx)
		{
			int x = xx + xxx;
			int y = yy + yyy;
			if ( x < 0 || y < 0 || x >= mMapSize || y >= mMapSize) continue;
			height += mMap[y * mMapSize + x].height;
			++count;
		}
		mMap[yyy*mMapSize+xxx].height = height / float(count);
	}

	// Calculate all the slopes
	Log::Print("Calculating slopes....");
	float* modSlopes = new float[mMapSize*mMapSize];
	float* modHeights = new float[mMapSize*mMapSize];
	float* texValues = new float[mMapSize*mMapSize*MAX_TEXTURES_PER_POS];
	for (int y=0; y<mMapSize; ++y)
	for (int x=0; x<mMapSize; ++x)
	{
		idx = y*mMapSize+x;

		// get neighbors
		float heights[9];
		float average = 0.0f;
		float lowest = 100000.0f;
		float highest = -10000.0f;
		for (int yy=-1; yy<=1; ++yy)
		for (int xx=-1; xx<=1; ++xx)
		{
			float h = GetMapPosSafe(x+xx,y+yy).height;
			heights[(yy+1)*3+(xx+1)] = h;
			average += h;
			if (h < lowest) lowest = h;
			if (h > highest) highest = h;
		}
		average *= 0.11f;

		// fill in the values
		modHeights[idx] = Clamp(0.001f, 0.999f, (GetMapPosSafe(x,y).height + average) * -0.1f);
		modSlopes[idx] = Clamp(0.001f, 0.999f, 0.5f * fabs(mMap[idx].height - average));
		for (int i=0; i<MAX_TEXTURES_PER_POS; ++i) texValues[idx*MAX_TEXTURES_PER_POS+i] = 0.0f;
	}

	// Create Textures
	Log::Print("Texturing....");
	for (int yy=-3; yy<=3; ++yy)
	for (int xx=-3; xx<=3; ++xx)
	{
		for (int yyy=0; yyy<mMapSize; ++yyy)
		for (int xxx=0; xxx<mMapSize; ++xxx)
		{
			// get the idx
			int x = xx + xxx;
			int y = yy + yyy;
			if ( x < 0 || y < 0 || x >= mMapSize || y >= mMapSize) continue;
			idx = yyy * mMapSize + xxx;
			int mapIdx = y*mMapSize+x;

			// Add the textures
			MapZoneType* type = mZones[mMap[mapIdx].zone]->GetType();
			int textureCount = type->GetTextureCount();
			for (int i=0; i<textureCount; ++i)
			{
				MapZoneTexture* t = type->GetTexture(i);

				// Get the texture slot
				int texId = -1;
				for (int j=0; j<MAX_TEXTURES_PER_POS; ++j)
				{
					if (mMap[idx].textures[j].texId == t->GetTextureId() ||
						mMap[idx].textures[j].texId == 255)
					{
						texId = j;
						break;
					}				
				}
				if (texId == -1) break;

				// Add the texture
				float slope = modSlopes[mapIdx];
				float height = modHeights[mapIdx];
				if (t->GetHeightMax() >= height && t->GetHeightMin() <= height &&
					t->GetSlopeMax() >= slope && t->GetSlopeMin() <= slope)
				{
					float heightModifier = 4.0f * Max(0.25f,Min(t->GetHeightMax() - height, height - t->GetHeightMin()));
					float value = TerrainGetTextureAtPos(float(xxx), float(yyy), t->GetTextureId(), t->GetDensity() * heightModifier); 
					if (value > 0.0f)
					{
						mMap[idx].textures[texId].texId = t->GetTextureId();
						texValues[idx*MAX_TEXTURES_PER_POS+texId] += value;
					}
				}
			}	
		}
	}

	// now normalize the texture
	for (int i=0; i<mapSizeSqr; ++i)
	{
		float totalValue = 0.0f;
		for (int j=0; j<MAX_TEXTURES_PER_POS; ++j)
		{
			totalValue += texValues[i*MAX_TEXTURES_PER_POS+j];
		}
		for (int j=0; j<MAX_TEXTURES_PER_POS; ++j)
		{
			mMap[i].textures[j].value = Clamp(0, 255, int((texValues[i*MAX_TEXTURES_PER_POS+j] / totalValue) * 255.0f));
		}
	}

	// cleanup
	delete [] modHeights;
	delete [] modSlopes;
	delete [] texValues;

	// Calculate Normals
	Log::Print("Calculating Normals....");
	Vec3f* normals = new Vec3f[mMapSize*mMapSize];
	for (int y=0; y<mMapSize-1; ++y)
	for (int x=0; x<mMapSize-1; ++x)
	{
		float fx = float(x);
		float fy = float(y);

		int idx0 = y     * mMapSize + x;
		int idx1 = y     * mMapSize + x+1;
		int idx2 = (y+1) * mMapSize + x+1;
		int idx3 = (y+1) * mMapSize + x;

		Vec3f p0 = Vec3f(fx	    , mMap[idx0].height, fy     );
		Vec3f p1 = Vec3f(fx+1.0f, mMap[idx1].height, fy     );
		Vec3f p2 = Vec3f(fx+1.0f, mMap[idx2].height, fy+1.0f);
		Vec3f p3 = Vec3f(fx     , mMap[idx3].height, fy+1.0f);

		Vec3f n1 = GetNormal(p0, p1, p2);
		normals[idx0] += n1;
		normals[idx1] += n1;
		normals[idx2] += n1;

		Vec3f n2 = GetNormal(p0, p2, p3);
		normals[idx0] += n1;
		normals[idx2] += n1;
		normals[idx3] += n1;
	}
	for (int i=0; i<mapSizeSqr; ++i)
	{
		normals[i].Normalize();
		mMap[i].SetNormal(normals[i]);
	}
	delete [] normals;
}

}